package com.jinly.ryder.log.service.impl;

import com.jcraft.jsch.Session;
import com.jinly.ryder.common.error.SystemErrorType;
import com.jinly.ryder.common.utils.DateUtil;
import com.jinly.ryder.log.service.LogSearchService;
import com.jinly.ryder.log.service.model.ContextQuery;
import com.jinly.ryder.log.service.model.LogContent;
import com.jinly.ryder.log.service.model.SearchQuery;
import com.jinly.ryder.log.service.model.SearchResult;
import com.jinly.ryder.manage.service.AppInfoService;
import com.jinly.ryder.manage.service.AppInstanceService;
import com.jinly.ryder.manage.service.model.AppInfo;
import com.jinly.ryder.manage.service.model.AppInstance;
import com.jinly.ryder.ssh.command.GrepCommand;
import com.jinly.ryder.ssh.command.SedCommand;
import com.jinly.ryder.ssh.command.SshSessionUtil;
import com.jinly.ryder.ssh.command.model.FileInfo;
import com.jinly.ryder.ssh.command.model.LogLine;
import com.jinly.ryder.ssh.service.SshFileService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

@Slf4j
@Service("sshLogSearchService")
@RequiredArgsConstructor
public class SshLogSearchServiceImpl implements LogSearchService {

    private final static Pattern PATTERN = Pattern.compile("(\\d+-\\d+-\\d+\\s\\S+)\\s+(\\w+)\\s(\\d+)\\s---\\s\\[([^\\]]+)\\]\\s(.*)");
    private final SshFileService sshFileService;
    private final AppInfoService appInfoService;
    private final AppInstanceService appInstanceService;

    @Override
    public SearchResult search(SearchQuery query) {
        SearchResult result = new SearchResult();
        AppInfo appInfo = appInfoService.getAppInfo(query.getAppName());
        String logFileDir = appInfo.getLogSetting().getRootPath();
        String logFileName = appInfo.getLogSetting().getFileName();
        if (ObjectUtils.isEmpty(logFileDir) || ObjectUtils.isEmpty(logFileName)) return result;
        List<String> hosts = appInstanceService.list(query.getAppName(), null).stream().map(AppInstance::getHost).collect(Collectors.toList());
        if (ObjectUtils.isEmpty(hosts)) return result;
        CompletableFuture<List<LogContent>>[] futures = new CompletableFuture[hosts.size()];
        for (int i = 0; i < hosts.size(); i++) {
            String host = hosts.get(i);
            futures[i] = CompletableFuture.supplyAsync(() -> searchInstanceLog(host, logFileDir, logFileName, query));
        }
        CompletableFuture.allOf(futures);
        List<LogContent> allList = new ArrayList<>();
        for (CompletableFuture<List<LogContent>> future : futures) {
            try {
                allList.addAll(future.get());
            } catch (Exception e) {
                log.error("ssh日志搜索失败", e);
                SystemErrorType.INTERNAL_SERVER_ERROR.buildError();
            }
        }
        result.setLogContents(allList);
        return result;
    }

    private List<LogContent> searchInstanceLog(String host, String logFileDir, String logFileName, SearchQuery query) {
        Session sshSession = null;
        List<LogLine> allList = new ArrayList<>();
        try {
            List<FileInfo> fileList = sshFileService.fileList(host, 22, logFileDir);
            LocalDateTime begin = DateUtil.toLocal(query.getFrom());
            LocalDateTime end = DateUtil.toLocal(query.getTo());
            fileList = fileList.stream().filter(f -> f.getModifyTime().isAfter(begin)
                    && f.getModifyTime().isBefore(end)
                    && f.getFileName().startsWith(logFileName)
            ).collect(Collectors.toList());

            sshSession = SshSessionUtil.connectSession(host, 22);
            for (FileInfo fileInfo : fileList) {
                GrepCommand grepCommand = new GrepCommand(sshSession);
                String timeRegex = createTimeRegex(fileInfo.getModifyTime().toLocalDate(), begin, end);
                if (!ObjectUtils.isEmpty(query.getLevel())) {
                    timeRegex += " " + query.getLevel().toUpperCase();
                }
                List<LogLine> list = grepCommand.exec(fileInfo, timeRegex, query.getKeyword(), query.getSize() * 3);
                for (LogLine logLine : list) {
                    if (begin.isBefore(logLine.getTime()) && end.isAfter(logLine.getTime())) {
                        allList.add(logLine);
                    }
                }
                if (allList.size() > query.getSize()) break;
            }
        } catch (Exception e) {
            log.warn("ssh log close:{}", e.getMessage());
        } finally {
            SshSessionUtil.closeSession(sshSession);
        }
        allList.sort(Comparator.comparing(LogLine::getTime).thenComparing(LogLine::getLineNum).reversed());

        return allList.stream().map(line -> {
            LogContent content = parseLogLine(line.getText());
            content.setSource(host);
            content.setPkgId(host + "|" + line.getFileName());
            content.setPkgLogId(line.getLineNum());
            return content;
        }).collect(Collectors.toList());
    }

    private LogContent parseLogLine(String text) {
        LogContent logContent = new LogContent();
        Matcher m = PATTERN.matcher(text);
        if (m.find() && m.groupCount() == 5) {
            logContent.setTime(DateUtil.toTimestamp(DateUtil.parse(m.group(1))));
            logContent.setLevel(m.group(2));
            logContent.setContent(m.group(5));
        } else {
            logContent.setContent(text);
        }
        return logContent;
    }

    @Override
    public List<LogContent> logContextList(ContextQuery query) {
        Session sshSession = null;
        String[] pkgs = query.getPkgId().split("\\|");
        String host = pkgs[0];
        String fileName = pkgs[1];
        List<LogLine> lineList = new ArrayList<>();
        long beginNum = (query.getPkgLogId() > query.getPrevLogs()) ? (query.getPkgLogId() - query.getPrevLogs()) : 1;
        try {
            sshSession = SshSessionUtil.connectSession(host, 22);
            SedCommand sedCommand = new SedCommand(sshSession);
            lineList = sedCommand.exec(fileName, beginNum, query.getPkgLogId() + query.getNextLogs());
        } catch (Exception e) {
            log.warn("ssh log close:{}", e.getMessage());
        } finally {
            SshSessionUtil.closeSession(sshSession);
        }
        List<LogContent> result = new ArrayList<>();
        for (int i = 0; i < lineList.size(); i++) {
            LogContent content = parseLogLine(lineList.get(i).getText());
            content.setSource(host);
            content.setPkgId(query.getPkgId());
            content.setPkgLogId(beginNum + i);
            result.add(content);
        }
        return result;

    }

    private String createTimeRegex(LocalDate date, LocalDateTime begin, LocalDateTime end) {
        StringBuilder regex = new StringBuilder();
        if (begin.toLocalDate().isBefore(date)) {
            begin = LocalDateTime.of(date, LocalTime.MIN);
        }
        if (end.toLocalDate().isAfter(date)) {
            end = LocalDateTime.of(date, LocalTime.MAX);
        }
        regex.append(date).append(" ");
        if ((end.getHour() - begin.getHour()) > 0) {
            regex.append(createRangeStr(begin.getHour(), end.getHour())).append("\\:");
            regex.append("[0-9]{2}").append("\\:");
            regex.append("[0-9]{2}");
        } else if ((end.getMinute() - begin.getMinute()) > 0) {
            regex.append(end.getHour() >= 10 ? ("" + end.getHour()) : ("0" + end.getHour())).append("\\:");
            regex.append(createRangeStr(begin.getMinute(), end.getMinute())).append("\\:");
            regex.append("[0-9]{2}");
        } else if ((end.getSecond() - begin.getSecond()) > 0) {
            regex.append(end.getHour() >= 10 ? ("" + end.getHour()) : ("0" + end.getHour())).append(":");
            regex.append(end.getMinute() >= 10 ? ("" + end.getMinute()) : ("0" + end.getMinute())).append(":");
            regex.append(createRangeStr(begin.getSecond(), end.getSecond()));
        } else {
            regex.append(end.getHour() >= 10 ? ("" + end.getHour()) : ("0" + end.getHour())).append(":");
            regex.append(end.getMinute() >= 10 ? ("" + end.getMinute()) : ("0" + end.getMinute())).append(":");
            regex.append(end.getSecond() >= 10 ? ("" + end.getSecond()) : ("0" + end.getSecond()));
        }
        regex.append("\\.[0-9]{3}");
        return regex.toString();
    }

    private String createRangeStr(int begin, int end) {
        List<String> list = new ArrayList<>();
        for (int i = begin; i <= end; i++) {
            list.add(i >= 10 ? ("" + i) : ("0" + i));
        }
        return "(?:" + list.stream().collect(Collectors.joining("|")) + ")";
    }


}
